package org.jeecg.common.system.controller;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.vo.DictModel;
import org.jeecg.common.system.vo.FileInfo;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.system.vo.SysDepartModel;
import org.jeecg.common.util.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.ui.ModelMap;
import org.springframework.util.AntPathMatcher;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.List;

/**
 * 公共接口
 *
 * @Author scott
 * @since 2018-12-20
 */
@Slf4j
@RestController
@Validated
@Api(tags = "公共接口")
@RequestMapping("/sys/common")
public class CommonController {
    /**
     * 下载签名秘钥
     */
    private final String secret = "YzM78myOqnvFjrtMlr2cem7OByjhCr3g";

    @Autowired
    private ISysBaseAPI sysBaseApi;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 本地：local minio：minio 阿里：alioss
     */
    @Value(value = "${jeecg.uploadType}")
    private String uploadType;

    /**
     * @return
     * @Author 政辉
     */
    @GetMapping("/403")
    public Result<?> noauth() {
        return Result.error("没有权限，请联系管理员授权");
    }

    /**
     * 获取预生成ID
     *
     * @return
     */
    @GetMapping("/preCreateId")
    @ApiOperation(value = "获取预生成ID", notes = "获取预生成ID")
    public Result<?> getId() {
        return Result.ok((Object) IdUtil.createSnowflake(5, 5).nextIdStr());
    }

    /**
     * 文件上传统一方法
     *
     * @param request
     * @param response
     * @return
     */
    @PostMapping(value = "/upload")
    @AutoLog(logType = 2, ty = "upload", value = "上传文件")
    @ApiOperation(value = "文件上传统一方法", notes = "文件上传统一方法")
    public Result<?> upload(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String token = request.getHeader("X-Access-Token");
        String username = JwtUtil.getUsername(token);
        LoginUser sysUser = sysBaseApi.getUserByName(username);
        if (StrUtil.isBlank(token)) {
            throw new JeecgBootException(401, "Token失效，请重新登录!", null);
        }
        if (sysUser == null) {
            throw new JeecgBootException(401, "Token失效，请重新登录!", null);
        }
        token = (String) redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token);
        if (!JwtUtil.verify(token, sysUser.getUsername(), sysUser.getPassword())) {
            throw new JeecgBootException(401, "Token失效，请重新登录!", null);
        }
        Result<Object> result = new Result<>();
        String bizPath = request.getParameter("biz");
        String fileType = request.getParameter("fileType");
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        // 获取上传文件对象
        MultipartFile file = multipartRequest.getFile("file");
        List<DictModel> models = sysBaseApi.queryDictItemsByCode("upload_type");
        log.info("{}", file.getBytes().length);
        //后缀校验
        DictModel model = models.stream().filter(elem -> StringUtils.equals("write_list", elem.getText())).findFirst().orElseGet(null);
        String name = file.getOriginalFilename();
        if (BrowserUtils.isIe(request)) {
            name = FileUtil.getName(name);
        }
        String ext = name.substring(name.lastIndexOf(".") + 1);
        if (model != null) {
            String[] exts = model.getValue().split(",");
            if (!ArrayUtils.contains(exts, ext)) {
                return Result.error("文件不合法");
            }
        }
        if (oConvertUtils.isEmpty(bizPath)) {
            if (CommonConstant.UPLOAD_TYPE_OSS.equals(uploadType)) {
                //未指定目录，则用阿里云默认目录 upload
                bizPath = "upload";
            } else {
                bizPath = "";
            }
        }
        if (CommonConstant.UPLOAD_TYPE_LOCAL.equals(uploadType)) {
            //针对jeditor编辑器如何使 lcaol模式，采用 base64格式存储
            String jeditor = request.getParameter("jeditor");
            if (oConvertUtils.isNotEmpty(jeditor)) {
                result.setMessage(CommonConstant.UPLOAD_TYPE_LOCAL);
                result.setSuccess(true);
                return result;
            } else {
                Object obj = sysBaseApi.uploadToLocal(file.getBytes(), bizPath, name, Convert.toInt(fileType), true);
                result.setSuccess(true);
                result.setResult(obj);
            }
        }
        return result;
    }

    /**
     * 预览图片&下载文件
     * 请求地址：http://localhost:8080/common/static/{user/20190119/e1fe9925bc315c60addea1b98eb1cb1349547719_1547866868179.jpg}
     *
     * @param fileId   文件id
     * @param sign     文件签名
     * @param response
     */
    @GetMapping(value = "/static/{fileId}")
    @ApiOperation(value = "预览图片&下载文件", notes = "预览图片&下载文件")
    public ResponseEntity view(@PathVariable String fileId, @NotBlank(message = "签名不能为空") String sign, HttpServletResponse response) {
        FileInfo fileInfo = sysBaseApi.getFileById(fileId);
        //是否为公共资源
        boolean containsPublic = StrUtil.contains(fileInfo.getFilePath(), "public");
        if (!containsPublic) {
            String text = SecureUtil.aes(secret.getBytes()).decryptStr(sign);
            JSONObject obj = JSON.parseObject(text);
            Assert.isTrue(StrUtil.equals(fileId, obj.getString("fileId")), "签名错误");
            //签名时间+3分钟
            long timestamp = obj.getLongValue("timestamp") + 180000;
            long time = timestamp - System.currentTimeMillis();
            Assert.isTrue(time > 0, "签名错误");
        }

        // ISO-8859-1 ==> UTF-8 进行编码转换
        if (StringUtils.isBlank(fileId)) {
            response.setStatus(401);
            throw new JeecgBootException("非法下载请求");
        }
        File file = new File(fileInfo.getFilePath());
        if (!file.exists()) {
            response.setStatus(404);
            throw new JeecgBootException("文件不存在");
        }
        String realName = "";
        if (StringUtils.isBlank(fileInfo.getFileLastName())) {
            //导出
            realName = fileInfo.getFileRealName();
        } else {
            //下载
            realName = StrUtil.format("{}.{}", fileInfo.getFileRealName(), fileInfo.getFileLastName());
        }

        ByteArrayResource resource = new ByteArrayResource(FileUtil.readBytes(file));
        HttpHeaders headers = new HttpHeaders();
        headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
        headers.add("Content-Disposition", "attachment; filename=" + URLUtil.encode(realName, "UTF-8"));
        headers.add("Pragma", "no-cache");
        headers.add("Expires", "0");
        return ResponseEntity.ok().headers(headers).contentLength(resource.contentLength()).contentType(MediaType.APPLICATION_OCTET_STREAM).body(resource);
    }

    /**
     * 获取文件下载签名
     *
     * @param modelMap
     * @return
     */
    @PostMapping("/sign")
    public Result sign(@RequestBody ModelMap modelMap) {
        Object user = SecurityUtils.getSubject().getPrincipal();
        if (user == null) {
            user = sysBaseApi.getPersonalUser();
        }
        Assert.notNull(user, "用户未登录");
        String strObj = JSON.toJSONString(user);
        LoginUser loginUser = JSON.parseObject(strObj, LoginUser.class);
        modelMap.put("username", loginUser.getUsername());
        modelMap.put("random", RandomStringUtils.randomAlphanumeric(16));
        modelMap.put("timestamp", System.currentTimeMillis());
        String sign = SecureUtil.aes(secret.getBytes()).encryptHex(JSON.toJSONString(modelMap));
        return Result.ok((Object) sign);
    }

    /**
     * 下载文件
     * 请求地址：http://localhost:8080/common/download/{user/20190119/e1fe9925bc315c60addea1b98eb1cb1349547719_1547866868179.jpg}
     *
     * @param request
     * @param response
     * @throws Exception
     */
    //	@GetMapping(value = "/download/**")
    //	public void download(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //		// ISO-8859-1 ==> UTF-8 进行编码转换
    //		String filePath = extractPathFromPattern(request);
    //		// 其余处理略
    //		InputStream inputStream = null;
    //		OutputStream outputStream = null;
    //		try {
    //			filePath = filePath.replace("..", "");
    //			if (filePath.endsWith(",")) {
    //				filePath = filePath.substring(0, filePath.length() - 1);
    //			}
    //			String localPath = uploadpath;
    //			String downloadFilePath = localPath + File.separator + filePath;
    //			File file = new File(downloadFilePath);
    //	         if (file.exists()) {
    //	         	response.setContentType("application/force-download");// 设置强制下载不打开            
    //	 			response.addHeader("Content-Disposition", "attachment;fileName=" + new String(file.getName().getBytes("UTF-8"),"iso-8859-1"));
    //	 			inputStream = new BufferedInputStream(new FileInputStream(file));
    //	 			outputStream = response.getOutputStream();
    //	 			byte[] buf = new byte[1024];
    //	 			int len;
    //	 			while ((len = inputStream.read(buf)) > 0) {
    //	 				outputStream.write(buf, 0, len);
    //	 			}
    //	 			response.flushBuffer();
    //	         }
    //
    //		} catch (Exception e) {
    //			log.info("文件下载失败" + e.getMessage());
    //			// e.printStackTrace();
    //		} finally {
    //			if (inputStream != null) {
    //				try {
    //					inputStream.close();
    //				} catch (IOException e) {
    //					e.printStackTrace();
    //				}
    //			}
    //			if (outputStream != null) {
    //				try {
    //					outputStream.close();
    //				} catch (IOException e) {
    //					e.printStackTrace();
    //				}
    //			}
    //		}
    //
    //	}

    /**
     * pdf预览Iframe
     *
     * @param modelAndView
     * @return
     * @功能：pdf预览Iframe
     */
    @GetMapping("/pdf/pdfPreviewIframe")
    @ApiOperation(value = "pdf预览Iframe", notes = "pdf预览Iframe")
    public ModelAndView pdfPreviewIframe(ModelAndView modelAndView) {
        modelAndView.setViewName("pdfPreviewIframe");
        return modelAndView;
    }

    /**
     * 把指定URL后的字符串全部截断当成参数
     * 这么做是为了防止URL中包含中文或者特殊字符（/等）时，匹配不了的问题
     *
     * @param request
     * @return
     */
    private static String extractPathFromPattern(final HttpServletRequest request) {
        String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
        String bestMatchPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
        return new AntPathMatcher().extractPathWithinPattern(bestMatchPattern, path);
    }

    /**
     * 中转HTTP请求，解决跨域问题
     *
     * @param url 必填：请求地址
     * @return
     */
    @GetMapping("/transitRESTful")
    @ApiOperation(value = "中转HTTP请求，解决跨域问题", notes = "中转HTTP请求，解决跨域问题")
    public Result transitRestFul(@RequestParam("url") String url, HttpServletRequest request) {
        try {
            ServletServerHttpRequest httpRequest = new ServletServerHttpRequest(request);
            // 中转请求method、body
            HttpMethod method = httpRequest.getMethod();
            JSONObject params;
            try {
                params = JSON.parseObject(JSON.toJSONString(httpRequest.getBody()));
            } catch (Exception e) {
                params = new JSONObject();
            }
            // 中转请求问号参数
            JSONObject variables = JSON.parseObject(JSON.toJSONString(request.getParameterMap()));
            variables.remove("url");
            // 在 headers 里传递Token
            String token = TokenUtils.getTokenByRequest(request);
            HttpHeaders headers = new HttpHeaders();
            headers.set("X-Access-Token", token);
            // 发送请求
            String httpUrl = URLDecoder.decode(url, "UTF-8");
            ResponseEntity<String> response = RestUtil.request(httpUrl, method, headers, variables, params, String.class);
            // 封装返回结果
            Result<Object> result = new Result<>();
            int statusCode = response.getStatusCodeValue();
            result.setCode(statusCode);
            result.setSuccess(statusCode == 200);
            String responseBody = response.getBody();
            try {
                // 尝试将返回结果转为JSON
                Object json = JSON.parse(responseBody);
                result.setResult(json);
            } catch (Exception e) {
                // 转成JSON失败，直接返回原始数据
                result.setResult(responseBody);
            }
            return result;
        } catch (Exception e) {
            log.debug("中转HTTP请求失败", e);
            return Result.error(e.getMessage());
        }
    }

    /**
     * 获取文件下载链接
     *
     * @param id 文件id
     * @return
     */
    @GetMapping("/downloadUrl")
    @ApiOperation(value = "获取文件下载链接", notes = "获取文件下载链接")
    @Deprecated
    public Result<?> downloadUrl(String id) {
        FileInfo fileInfo = sysBaseApi.getFileById(id);
        if (fileInfo == null) {
            return Result.error("文件不存在");
        }
        String uuid = IdUtil.fastSimpleUUID();
        String url = "/sys/common/static/" + uuid;
        List<DictModel> models = sysBaseApi.queryDictItemsByCode("download_expir_time");
        redisUtil.set(CommonConstant.DOWNLOAD_TOKEN + uuid, fileInfo, Convert.toInt(models.get(0).getValue()));
        return Result.ok((Object) url);
    }

    /**
     * 获取文件下载对象[推荐]
     *
     * @param id 文件id
     * @return
     */
    @GetMapping("/v2/downloadUrl")
    @ApiOperation(value = "获取文件下载链接", notes = "获取文件下载链接")
    public Result<?> downloadUrlV2(String id) {
        FileInfo fileInfo = sysBaseApi.getFileById(id);
        if (fileInfo == null) {
            return Result.error("文件不存在");
        }
        Result<Object> url = (Result<Object>) this.downloadUrl(id);
        fileInfo.setUrl(url.getResult().toString());
        fileInfo.setFileName("保密");
        return Result.ok(fileInfo);
    }

    /**
     * 根绝用户id查询所在部门信息
     *
     * @param userId 用户id
     * @return
     */
    @GetMapping("/query/dept/by/userId")
    public Result<SysDepartModel> queryDeptByUserId(String userId) {
        JSONObject jsonObject = sysBaseApi.findDeptByUserId(userId);
        SysDepartModel sysDepartModel = jsonObject.toJavaObject(SysDepartModel.class);
        return Result.ok(sysDepartModel);
    }
}
